// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ImportExportServiceAdatper.cs" company="">
//   
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace ExecutionTool.ImportExport
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Runtime.Serialization;
    using System.Web;

    using DacIEServiceCli.IEService;

    using ExecutionTool.Common;
    using ExecutionTool.CopyDatabase;
    using ExecutionTool.Profile;

    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.ServiceLocation;

    public class ImportExportServiceAdatper : IImportExportServiceAdatper
    {
        #region Fields

        private readonly LogWriter logger;

        #endregion

        #region Constructors and Destructors

        public ImportExportServiceAdatper(LogWriter logger = null)
        {
            this.logger = logger ?? ServiceLocator.Current.GetInstance<LogWriter>();
        }

        #endregion

        #region Public Methods and Operators

        public double GetOperationProgress(string endpoint, SqlAzureLocation source, Guid statusTrackId, StepType stepType)
        {
            this.logger.LogVerbose(
                string.Format("Getting [{0}] operation progress. [{1}][{2}][{3}]...", stepType, endpoint, source.ServerName, statusTrackId));

            var uriBuilder = string.Format(
                "{4}/Status?servername={0}&username={1}&password={2}&reqId={3}", 
                HttpUtility.UrlEncode(source.ServerName), 
                HttpUtility.UrlEncode(source.UserName), 
                HttpUtility.UrlEncode(source.Password), 
                statusTrackId.ToString("D"), 
                endpoint);

            var uri = new Uri(uriBuilder);

            var result = this.ExecuteHelper(
                uri, 
                Constants.HttpGet, 
                null, 
                respStream =>
                    {
                        var dcs = new DataContractSerializer(typeof(List<StatusInfo>));
                        var statusInfoList = (List<StatusInfo>)dcs.ReadObject(respStream);

                        if (statusInfoList.Count <= 0)
                        {
                            throw new StepFaultException(
                                string.Format("Status for the specified request [{0}] not found.", statusTrackId), 
                                stepType, 
                                ErrorCodes.ImportExportRequestNotFoundError);
                        }

                        var statusInfo = statusInfoList[0];

                        if (!string.IsNullOrWhiteSpace(statusInfo.ErrorMessage))
                        {
                            throw new StepFaultException(statusInfo.ErrorMessage, stepType, ErrorCodes.ImportExportError);
                        }

                        // Status: Running, Progress = 90%
                        // Status: Running, Progress = 47.5%
                        // Status: Completed
                        // Status: Failed
                        var status = statusInfo.Status;

                        if (status.Contains("Completed"))
                        {
                            return 100;
                        }

                        if (status.Contains("Running"))
                        {
                            var progressText = status.Split('=')[1].Trim(' ', '%');
                            double progress;
                            return double.TryParse(progressText, out progress) ? progress : 0;
                        }

                        return 0;
                    });

            this.logger.LogVerbose(
                string.Format("Got [{0}] operation progress ({4}%). [{1}][{2}][{3}].", stepType, endpoint, source.ServerName, statusTrackId, result));

            return result;
        }

        public void TestConnection(string endpoint)
        {
            this.logger.LogVerbose(string.Format("Testing SQL Azure Import/Export service endpoint [{0}]...", endpoint));

            var uri = new Uri(endpoint + "/Test");

            this.ExecuteHelper(
                uri, 
                Constants.HttpGet, 
                null, 
                responseStream =>
                    {
                        var dcs = new DataContractSerializer(typeof(int));
                        var result = (int)dcs.ReadObject(responseStream);
                        return result;
                    });

            this.logger.LogVerbose(string.Format("Tested SQL Azure Import/Export service endpoint [{0}].", endpoint));
        }

        public Guid TriggerExport(
            string endpoint, 
            SqlAzureLocation sourceSqlAzureLocation, 
            AzureStorageLocation targetAzureStorage, 
            string blobUri, 
            string databaseNameToExport)
        {
            this.logger.LogVerbose(
                string.Format("Tiggering export service operation. [{0}][{1}][{2}]...", endpoint, sourceSqlAzureLocation.ServerName, blobUri));

            var uri = new Uri(endpoint + "/Export");

            var result = this.ExecuteHelper(
                uri, 
                Constants.HttpPost, 
                reqStream =>
                    {
                        var serializer = new DataContractSerializer(typeof(ExportInput));
                        serializer.WriteObject(reqStream, GetExportInput(sourceSqlAzureLocation, targetAzureStorage, blobUri, databaseNameToExport));
                    }, 
                respStream =>
                    {
                        var dcs = new DataContractSerializer(typeof(Guid));
                        var resultGuid = (Guid)dcs.ReadObject(respStream);
                        return resultGuid;
                    });

            this.logger.LogVerbose(
                string.Format("Tiggered export service operation. [{0}][{1}][{2}].", endpoint, sourceSqlAzureLocation.ServerName, blobUri));

            return result;
        }

        public Guid TriggerImport(
            string endpoint, 
            SqlAzureLocation targetSqlAzureLocation, 
            AzureStorageLocation sourceAzureStorage, 
            string blobUri, 
            SqlAzureDatabaseInfo databaseInfo, 
            string databaseNameToImport)
        {
            this.logger.LogVerbose(
                string.Format("Tiggering import service operation. [{0}][{1}][{2}]...", endpoint, targetSqlAzureLocation.ServerName, blobUri));

            var uri = new Uri(endpoint + "/Import");

            var result = this.ExecuteHelper(
                uri, 
                Constants.HttpPost, 
                reqStream =>
                    {
                        var serializer = new DataContractSerializer(typeof(ImportInput));
                        serializer.WriteObject(
                            reqStream, GetImportInput(targetSqlAzureLocation, sourceAzureStorage, blobUri, databaseInfo, databaseNameToImport));
                    }, 
                respStream =>
                    {
                        var dcs = new DataContractSerializer(typeof(Guid));
                        var resultGuid = (Guid)dcs.ReadObject(respStream);
                        return resultGuid;
                    });

            this.logger.LogVerbose(
                string.Format("Tiggered import service operation. [{0}][{1}][{2}].", endpoint, targetSqlAzureLocation.ServerName, blobUri));

            return result;
        }

        #endregion

        #region Methods

        private static ConnectionInfo GetConnectionInfo(SqlAzureLocation source, string databaseNameToImportExport)
        {
            return new ConnectionInfo
                {
                   ServerName = source.ServerName, UserName = source.UserName, Password = source.Password, DatabaseName = databaseNameToImportExport, 
                };
        }

        private static ExportInput GetExportInput(SqlAzureLocation source, AzureStorageLocation target, string blobUri, string databaseNameToExport)
        {
            var ei = new ExportInput
                {
                    ConnectionInfo = GetConnectionInfo(source, databaseNameToExport), 
                    BlobCredentials = new BlobStorageAccessKeyCredentials { StorageAccessKey = target.AccessKey, Uri = blobUri, }, 
                };

            return ei;
        }

        private static ImportInput GetImportInput(
            SqlAzureLocation targetSqlAzureLocation, 
            AzureStorageLocation sourceAzureStorage, 
            string blobUri, 
            SqlAzureDatabaseInfo databaseInfo, 
            string databaseNameToImport)
        {
            var ii = new ImportInput
                {
                    ConnectionInfo = GetConnectionInfo(targetSqlAzureLocation, databaseNameToImport), 
                    BlobCredentials = new BlobStorageAccessKeyCredentials { StorageAccessKey = sourceAzureStorage.AccessKey, Uri = blobUri, }, 
                    AzureEdition = databaseInfo.Edition.ToString(), 
                    DatabaseSizeInGB = Convert.ToInt32(databaseInfo.MaxSizeInGb)
                };
            return ii;
        }

        private T ExecuteHelper<T>(Uri operationUri, string method, Action<Stream> postFactory, Func<Stream, T> responseProcessFactory)
        {
            this.logger.LogVerbose(string.Format("Requesting endpoint [{0}]...", operationUri.GetLeftPart(UriPartial.Path)));

            var req = WebRequest.Create(operationUri);
            req.Method = method;
            req.ContentType = "application/xml";

            if (postFactory != null)
            {
                using (var reqStream = req.GetRequestStream())
                {
                    postFactory(reqStream);
                }
            }

            if (responseProcessFactory != null)
            {
                using (var response = req.GetResponse())
                {
                    using (var stream = response.GetResponseStream())
                    {
                        return responseProcessFactory(stream);
                    }
                }
            }

            return default(T);
        }

        #endregion

        private sealed class Constants
        {
            #region Constants

            public const string HttpGet = "GET";

            public const string HttpPost = "POST";

            #endregion
        }
    }
}
